﻿// HSS.Security.KeyManager.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       KeyManager.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/04/2010
// ----------------------------------------------------------------------------
namespace HSS.Security
{
	#region Using Directives
	using System;
	using System.Diagnostics;
	using System.Runtime.InteropServices;
	using System.Security.Cryptography;
	using System.Security.Principal;
	using System.Windows.Forms;
	using HSS.Forms;
	using HSS.Logging;
	using HSS.Security.Cryptography;
	#endregion

	#region KeyManager
	/// <summary>
	/// The KeyManager enables administrators to manage the Keys on this machine.
	/// </summary>
	internal partial class KeyManager : Form
	{
		#region Fields
		static bool running = false;
		bool storeLoaded = false;
		bool useMachineStore = false;
		bool isElevated = false;
		bool isInError = false;
		KeyContainerService keyMgr;
		#endregion

		#region Constructor
		/// <summary>
		/// Default CTOR
		/// </summary>
		public KeyManager()
		{
			InitializeComponent();
			if (Environment.OSVersion.Version.Major >= 6)
			{
				if (!IsAdmin())
				{
					this.lblKeyList.Text = "Key containers in users Profile:";
					AddShieldToButton(btnMachineStore);
				}
				else
				{
					this.isElevated = true;
					this.useMachineStore = true;
					this.lblKeyList.Text = "Key containers on this machine:";
				}
			}
			else
			{
				this.lblKeyList.Text = "Key containers in UserProfile:";
			}
		}
		#endregion

		#region Methods

		#region Elevation

		internal static bool IsAdmin()
		{
			WindowsIdentity id = WindowsIdentity.GetCurrent();
			WindowsPrincipal p = new WindowsPrincipal(id);
			return p.IsInRole(WindowsBuiltInRole.Administrator);
		}
		internal static void AddShieldToButton(Button b)
		{
			b.FlatStyle = FlatStyle.System;
			SendMessage(b.Handle, BCM_SETSHIELD, 0, 0xFFFFFFFF);
		}
		internal static void RestartElevated()
		{
			ProcessStartInfo startInfo = new ProcessStartInfo();
			startInfo.UseShellExecute = true;
			startInfo.WorkingDirectory = Environment.CurrentDirectory;
			startInfo.FileName = Application.ExecutablePath;
			startInfo.Verb = "runas";
			try { Process p = Process.Start(startInfo); }
			catch (System.ComponentModel.Win32Exception) { return; }
			Application.Exit();
		}

		[DllImport("user32")]
		public static extern UInt32 SendMessage(IntPtr hWnd, UInt32 msg, UInt32 wParam, UInt32 lParam);

		internal const int BCM_FIRST = 0x1600; //Normal button
		internal const int BCM_SETSHIELD = (BCM_FIRST + 0x000C);

		#endregion

		private void ToggleStore()
		{
			storeLoaded = false;
			keyMgr = null;

			if (Environment.OSVersion.Version.Major >= 6)
			{
				if (!IsAdmin())
				{
					RestartElevated();
					return;
				}
				else
				{
					useMachineStore = !useMachineStore;
				}
			}
			else
			{
				useMachineStore = !useMachineStore;
			}

			this.RefreshList();
		}
		private void LoadStore()
		{
			storeLoaded = false;
			if (useMachineStore)
			{
				keyMgr = new KeyContainerService(useMachineStore, true);
				AES.ConfigureAccess(useMachineStore, true);
				this.Text = "HSS Key Manager [ Machine Store ]";
				this.lblKeyList.Text = "Key containers on this machine:";
				storeLoaded = true;
			}
			else
			{
				keyMgr = new KeyContainerService(useMachineStore, true);
				AES.ConfigureAccess(useMachineStore, true);
				this.Text = "HSS Key Manager [ User Store ]";
				this.lblKeyList.Text = "Key containers in UserProfile:";
				storeLoaded = true;
			}

			if (isElevated)
				this.Text += " (Elevated)";
		}
		private void RefreshList()
		{
			if (!storeLoaded)
				this.LoadStore();
			if (!storeLoaded)
			{
				this.txtPublicKeyData.ResetText();
				this.txtbase64.ResetText();
				this.lstKeyContainers.Items.Clear();
				this.btnDelete.Enabled = false;
				this.btnExport.Enabled = false;
				this.grbKeys.Text = "Public Sharable Key Info";
				return;
			}
			this.ShowBusy();
			this.txtPublicKeyData.ResetText();
			this.txtbase64.ResetText();
			this.lstKeyContainers.Items.Clear();
			foreach (string name in this.keyMgr.GetAllKeys())
				this.lstKeyContainers.Items.Add(name);
			this.grbKeys.Text = "Public Sharable Key Info";
			this.EnableDisableButtons();
		}
		private void CreateNew()
		{
			string keyContainerName = this.txtNewContainerName.Text;
			int keySizeInBits = int.Parse(this.udNewKeyBitLength.Text);

			if (keySizeInBits >= 4096 &&
				DialogResult.Cancel ==
				MessageBox.Show(this, "Longer key lengths take awhile to generate, so please be patient...", Application.ProductName, MessageBoxButtons.OKCancel))
				return;

			this.tssProgress.Visible = true;
			this.tssStatus.Text = "Generating new MachineKey...";
			this.grbCreate.Enabled = false;
			this.pnlKeyList.Enabled = false;

			System.Threading.ThreadPool.QueueUserWorkItem(this.CreateNew, new object[] { keyContainerName, keySizeInBits });
		}
		private void CreateNew(object param)
		{
			object[] prm = param as object[];

			string keyName = (string)prm[0];
			int keySize = (int)prm[1];

			running = true;

			// Show progress bar...
			new System.Threading.Thread(StartProgress).Start();

			// Create the key
			RSACryptoServiceProvider csp = keyMgr.CreateNew(keyName, keySize);

			// Stop the progressbar
			running = false;

		}
		private void StartProgress()
		{
			while (running)
			{
				this.UpdateProgress();
				System.Threading.Thread.Sleep(300);
				Application.DoEvents();
			}
			this.CloseProgress();
		}
		private void UpdateProgress()
		{
			if (this.InvokeRequired)
			{
				this.Invoke(new MethodInvoker(UpdateProgress));
				return;
			}

			if (this.tssProgress.Value >= this.tssProgress.Maximum)
				this.tssProgress.Value = 0;
			this.tssProgress.PerformStep();
			Application.DoEvents();
		}
		private void CloseProgress()
		{
			if (this.InvokeRequired)
			{
				this.Invoke(new MethodInvoker(CloseProgress));
				return;
			}

			this.tssProgress.Value = 0;
			this.tssProgress.Visible = false;
			Application.DoEvents();
			RefreshList();
			this.grbCreate.Enabled = true;
			this.pnlKeyList.Enabled = true;
			this.udNewKeyBitLength.SelectedIndex = 3;
			this.txtNewContainerName.ResetText();
			this.Left += 4;
			System.Threading.Thread.Sleep(340);
			this.Left -= 4;
			this.tssStatus.Text = "Ready";
		}
		private void ImportKey()
		{
			string prompt = "Enter the PrivateKey Base64 Text";
			string title = "Import PrivateKey";
			InputDialogResult res =
				InputDialog.Show(prompt, title, string.Empty, delegate(object source, InputDialogValidatingArgs args)
				{
					if (args.Text.Length == 0)
					{
						args.Message = "You must enter the Private Key information.";
						args.Cancel = true;
					}
				});
			if (res.OK)
			{
				if (keyMgr.Exists(this.txtNewContainerName.Text))
				{
					MessageBox.Show("A KeyContainer already exists by that name", "Key Manager", MessageBoxButtons.OK, MessageBoxIcon.Error);
					return;
				}
				byte[] privateKey = Convert.FromBase64String(res.Text);
				if (this.keyMgr.ImportKey(this.txtNewContainerName.Text, privateKey))
				{
					this.txtNewContainerName.ResetText();
					this.RefreshList();
					MessageBox.Show("Success importing private key.", "Import", MessageBoxButtons.OK, MessageBoxIcon.Information);
				}
				else
				{
					MessageBox.Show("Failed importing key for unknown reason.", "Import", MessageBoxButtons.OK, MessageBoxIcon.Information);
				}
			}
		}
		private void ExportKey()
		{
			int i = this.lstKeyContainers.SelectedIndex;
			if (i > -1)
			{
				string msg = "Are you sure you want to Export the Private Key?";
				if (MessageBox.Show(msg, "Security Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
				{
					string name = (string)this.lstKeyContainers.Items[i];
					RSACryptoServiceProvider rsa = this.keyMgr.LoadKey(name);
					string privateKey = Convert.ToBase64String(rsa.ExportCspBlob(true));
					string prompt = "PrivateKey for [ " + name + " ]";
					string title = "WARNING - Private Key - USE CAUTION";
					InputDialog.Show(prompt, title, privateKey, null);
				}
			}
		}
		private void TestEncrypt()
		{
			int i = lstKeyContainers.SelectedIndex;
			if (i > -1)
			{
				string name = (string)this.lstKeyContainers.Items[i];
				byte[] publicKey = Convert.FromBase64String(this.txtbase64.Text);

				string prompt = "Enter Plain Text...";
				string title = "Test Encrypt";
				InputDialogResult res =
					InputDialog.Show(prompt, title, string.Empty, delegate(object source, InputDialogValidatingArgs args)
					{
						if (args.Text.Length == 0)
						{
							args.Message = "You must enter some text to encrypt.";
							args.Cancel = true;
						}
					});
				if (res.OK)
				{
					string cipher = AES.Encrypt(res.Text, publicKey);
					Clipboard.Clear();
					Clipboard.SetData(DataFormats.Text, cipher);
					MessageBox.Show("Success encrypting text. The encrypted string has been placed on the clipboard.", title, MessageBoxButtons.OK, MessageBoxIcon.Information);
				}
			}
		}
		private void TestDecrypt()
		{
			int i = lstKeyContainers.SelectedIndex;
			if (i > -1)
			{
				string name = (string)lstKeyContainers.Items[i];
				string prompt = "Enter Encrypted Text...";
				string title = "Test Decrypt";
				InputDialogResult res =
				InputDialog.Show(prompt, title, string.Empty, delegate(object source, InputDialogValidatingArgs args)
				{
					if (args.Text.Length == 0)
					{
						args.Message = "You must enter the encrypt text.";
						args.Cancel = true;
					}
				});
				if (res.OK)
				{
					AES.DefaultKeyName = name;
					try
					{
						string text = AES.Decrypt(res.Text);
						string msg = "If the decryption was successful, your original text is below..." + Environment.NewLine + Environment.NewLine;
						MessageBox.Show(msg + text, title, MessageBoxButtons.OK, MessageBoxIcon.Information);
					}
					catch (Exception ex)
					{
						MessageBox.Show(ex.Message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
					}
				}
			}
		}
		private void TestSharedKeyEncrypt()
		{
			string prompt = "Enter password";
			string title = "Test Encrypt";
			InputDialogResult res =
				InputDialog.Show(prompt, title, string.Empty, delegate(object source, InputDialogValidatingArgs args)
				{
					if (args.Text.Length == 0)
					{
						args.Message = "You must enter the password.";
						args.Cancel = true;
					}
				});
			byte[] sharedKey;
			byte[] iv;
			if (res.OK)
			{
				sharedKey = AES.DeriveBytes(res.Text, "ThisIsSomeGoodSalt", 10, 16);
				iv = AES.DeriveBytes("ThisIsTheIV", "ThisIsSomeGoodSalt", 10, 16);
				prompt = "Enter Plain Text...";
				title = "Test Encrypt";
				res = InputDialog.Show(prompt, title, string.Empty, delegate(object source, InputDialogValidatingArgs args)
					{
						if (args.Text.Length == 0)
						{
							args.Message = "You must enter some text to encrypt.";
							args.Cancel = true;
						}
					});
				if (res.OK)
				{
					string cipher = AES.Encrypt(res.Text, sharedKey, iv);
					Clipboard.Clear();
					Clipboard.SetData(DataFormats.Text, cipher);
					MessageBox.Show("Success encrypting text. The encrypted string has been placed on the clipboard.", title, MessageBoxButtons.OK, MessageBoxIcon.Information);
				}
			}
		}
		private void TestSharedKeyDecrypt()
		{
			string prompt = "Enter password";
			string title = "Test Decrypt";
			InputDialogResult res =
				InputDialog.Show(prompt, title, string.Empty, delegate(object source, InputDialogValidatingArgs args)
				{
					if (args.Text.Length == 0)
					{
						args.Message = "You must enter the password.";
						args.Cancel = true;
					}
				});
			byte[] sharedKey;
			byte[] iv;
			if (res.OK)
			{
				sharedKey = AES.DeriveBytes(res.Text, "ThisIsSomeGoodSalt", 10, 16);
				iv = AES.DeriveBytes("ThisIsTheIV", "ThisIsSomeGoodSalt", 10, 16);
				prompt = "Enter Encrypted Text...";
				res = InputDialog.Show(prompt, title, string.Empty, delegate(object source, InputDialogValidatingArgs args)
				{
					if (args.Text.Length == 0)
					{
						args.Message = "You must enter some text to encrypt.";
						args.Cancel = true;
					}
				});
				if (res.OK)
				{
					try
					{
						string text = AES.Decrypt(res.Text, sharedKey, iv);
						string msg = "If the decryption was successful, your original text is below..." + Environment.NewLine + Environment.NewLine;
						MessageBox.Show(msg + text, title, MessageBoxButtons.OK, MessageBoxIcon.Information);
					}
					catch (Exception ex)
					{
						MessageBox.Show(ex.Message, title, MessageBoxButtons.OK, MessageBoxIcon.Error);
					}
				}
			}
		}
		private void Delete(int index)
		{
			if (-1 != index)
			{
				this.ShowBusy();
				string name = (string)this.lstKeyContainers.Items[index];
				if (DialogResult.Yes == MessageBox.Show(this, string.Format("Are you sure you want to delete key container {0}?", name), Application.ProductName, MessageBoxButtons.YesNoCancel))
				{
					this.keyMgr.Delete(name);
					this.txtPublicKeyData.ResetText();
					this.txtbase64.ResetText();
					this.RefreshList();
				}
			}
		}
		private void KeySelected(int index)
		{
			try
			{
				this.txtbase64.ResetText();
				this.txtPublicKeyData.ResetText();
				this.EnableDisableButtons();

				if (index > -1)
				{
					var name = (string)this.lstKeyContainers.Items[index];
					var rsa = this.keyMgr.LoadKey(name);
					if (name.Length > 40)
						name = name.Remove(40) + " ...";
					this.grbKeys.Text = "Key Name:  " + name + " ( " + rsa.KeySize.ToString() + "  bits )";
					this.txtbase64.Text = Convert.ToBase64String(rsa.ExportCspBlob(false));
					this.txtPublicKeyData.Text = rsa.ToXmlString(false);
					if (!rsa.CspKeyContainerInfo.Exportable)
						this.btnExport.Enabled = false;
					this.btnCopy.Enabled = true;
					this.btnTestEncrypt.Enabled = true;
					this.btnTestDecrypt.Enabled = true;
				}
				else
				{
					this.grbKeys.Text = "Public Sharable Key Info";
					this.btnTestEncrypt.Enabled = false;
					this.btnTestDecrypt.Enabled = false;
				}
			}
			catch (CryptographicException cex)
			{
				isInError = true;
				EnableDisableButtons();
				MessageBox.Show(cex.Message + "Please select a different item.", "Cryptographic Exception", MessageBoxButtons.OK, MessageBoxIcon.Warning);
			}
			catch (Exception ex)
			{
				isInError = true;
				EnableDisableButtons();
				MessageBox.Show("Unexpected error; please select a different item." + Environment.NewLine + ex.Message, "Unexpected error", MessageBoxButtons.OK, MessageBoxIcon.Error);
				Log.WriteException(ex);
			}
		}
		private void EnableDisableButtons()
		{
			bool selected = (-1 != this.lstKeyContainers.SelectedIndex);
			this.btnDelete.Enabled = selected && !isInError;
			this.btnExport.Enabled = selected && !isInError;
			this.btnCopy.Enabled = !isInError;
			this.btnExport.Enabled = !isInError;
			this.btnTestDecrypt.Enabled = !isInError;
			this.btnTestEncrypt.Enabled = !isInError;
		}
		private void ShowBusy()
		{
			Cursor.Current = Cursors.WaitCursor;
		}
		#endregion

		#region Event Handlers
		private void KeyManager_Shown(object sender, EventArgs e)
		{
			Application.DoEvents();
			this.RefreshList();
			this.udNewKeyBitLength.SelectedIndex = 3;
		}
		private void lstKeyContainers_SelectedIndexChanged(object sender, EventArgs e)
		{
			isInError = false;
			this.KeySelected(lstKeyContainers.SelectedIndex);
		}
		private void txtNewContainerName_TextChanged(object sender, EventArgs e)
		{
			this.btnCreateNew.Enabled = (this.txtNewContainerName.Text.Length > 0);
			this.btnImport.Enabled = this.btnCreateNew.Enabled;
		}
		private void btnCreateNew_Click(object sender, EventArgs e)
		{
			this.CreateNew();
		}
		private void btnRefresh_Click(object sender, EventArgs e)
		{
			RefreshList();
		}
		private void btnDelete_Click(object sender, EventArgs e)
		{
			this.Delete(lstKeyContainers.SelectedIndex);
		}
		private void btnCopy_Click(object sender, EventArgs e)
		{
			if (this.tabControl1.SelectedIndex == 0)
				Clipboard.SetData(DataFormats.Text, this.txtbase64.Text);
			else
				Clipboard.SetData(DataFormats.Text, this.txtPublicKeyData.Text);
			MessageBox.Show(this, "The Public Key has been placed on the clipboard.", "Copy Public Key", MessageBoxButtons.OK, MessageBoxIcon.Information);
		}
		private void btnImport_Click(object sender, EventArgs e)
		{
			this.ImportKey();
		}
		private void btnExport_Click(object sender, EventArgs e)
		{
			this.ExportKey();
		}
		private void btnTestEncrypt_Click(object sender, EventArgs e)
		{
			this.TestEncrypt();
			//this.TestSharedKeyEncrypt();
		}
		private void btnTestDecrypt_Click(object sender, EventArgs e)
		{
			this.TestDecrypt();
			//this.TestSharedKeyDecrypt();
		}
		private void btnMachineStore_Click(object sender, EventArgs e)
		{
			this.ToggleStore();
		}
		#endregion
	}
	#endregion
}